
package net.badcurrent.ldapodm;

import org.springframework.ldap.LdapTemplate;

import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import net.badcurrent.ldapodm.typeconverter.TypeConverter;
import net.badcurrent.ldapodm.typeconverter.IntegerTypeConverter;
import net.badcurrent.ldapodm.ldap.EntryAttributesMapper;
import net.badcurrent.ldapodm.ldap.DistinguishedNameFactory;
import net.badcurrent.ldapodm.config.ClassMetaData;
import net.badcurrent.ldapodm.annotations.LdapAttribute;
import net.badcurrent.ldapodm.annotations.StructuralObjectClass;
import net.badcurrent.ldapodm.annotations.ObjectClass;
import net.badcurrent.ldapodm.reflection.ReflectionUtils;

/**
 * Configure a <code>SessionFactory</code> from Java 5 annotated classes.
 *
 * @author John D'Emic <john.demic@gmail.com>
 */
public class AnnotationSessionFactory implements SessionFactory {

    private LdapTemplate ldapTemplate;
    private Map<Class, EntryAttributesMapper> typeMappers = new HashMap<Class, EntryAttributesMapper>();
    private Map<Class, DistinguishedNameFactory> distinguishedNameFactories =
            new HashMap<Class, DistinguishedNameFactory>();
    private String baseDn;
    private Map<Class, TypeConverter> typeConverters = new HashMap<Class, TypeConverter>();
    private Map<Class, ClassMetaData> classMetaDataMap = new HashMap<Class, ClassMetaData>();


    public AnnotationSessionFactory() {
        typeConverters = new HashMap<Class, TypeConverter>();
        typeConverters.put(Integer.class, new IntegerTypeConverter());
    }

    @SuppressWarnings({"unchecked"})
    private void initClassMetaData(Class clazz) {
        ClassMetaData cmd;

        cmd = new ClassMetaData(clazz);

        StructuralObjectClass soc = (StructuralObjectClass)
                clazz.getAnnotation(StructuralObjectClass.class);
        if (soc == null) {
            throw new ConfigurationException("Class must be annotated with @StructuralObjectClass: " +
                    clazz);
        }
        cmd.setStructuralObjectClass(soc.value());

        ObjectClass oc = (ObjectClass) clazz.getAnnotation(ObjectClass.class);
        for (String objectClass : oc.value()) {
            cmd.addObjectClass(objectClass);
        }

        for (Field field : cmd.getClazz().getDeclaredFields()) {
            if (field.getAnnotation(LdapAttribute.class) != null) {
                LdapAttribute attr = field.getAnnotation(LdapAttribute.class);
                for (String attrName : attr.value()) {
                    cmd.addAttribute(field.getName(), attrName);
                }
            }
        }

        for (Method method : cmd.getClazz().getMethods()) {
            if (method.getAnnotation(LdapAttribute.class) != null) {
                String propertyName = ReflectionUtils.getProperty(method);
                LdapAttribute attr = method.getAnnotation(LdapAttribute.class);
                for (String attrName : attr.value()) {
                    cmd.addAttribute(propertyName, attrName);
                }
            }
        }

        classMetaDataMap.put(clazz, cmd);
    }


    public Session openSession() {
        return new SessionImpl(ldapTemplate, typeMappers, distinguishedNameFactories,
                typeConverters, classMetaDataMap, baseDn);
    }

    public LdapTemplate getLdapTemplate() {
        return ldapTemplate;
    }

    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

    public Map<Class, EntryAttributesMapper> getTypeMappers() {
        return typeMappers;
    }

    public void setTypes(Class[] types) {
        for (Class clazz : types) {
            typeMappers.put(clazz, new EntryAttributesMapper(clazz));
            initClassMetaData(clazz);
        }
    }

    public Map<Class, DistinguishedNameFactory> getDistinguishedNameFactories() {
        return distinguishedNameFactories;
    }

    public void setDistinguishedNameFactories(Map<Class, DistinguishedNameFactory> distinguishedNameFactories) {
        this.distinguishedNameFactories = distinguishedNameFactories;
    }

    public String getBaseDn() {
        return baseDn;
    }

    public void setBaseDn(String baseDn) {
        this.baseDn = baseDn;
    }

    public void setTypeConverters(Map<Class, TypeConverter> typeConverters) {
        this.typeConverters.putAll(typeConverters);
    }

}
